﻿using System;
using System.Collections.Generic;
using System.Windows;

namespace Geometry
{
    [global::System.AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
    public sealed class GObjectGeneratorAttribute : Attribute
    {
        // This is a positional argument
        public GObjectGeneratorAttribute(string description)
        {
            this.Description = description;
        }

        public string Description { get; private set; }

        public string UIName { get; set; }

        public string StandardImage { get; set; }

        public string HoverImage { get; set; }
    }

    public static class GObjectFactory
    {
        #region Generators
        
        [GObjectGenerator("Creates a point free point at origin",
            UIName = "Free Point",
            StandardImage = "/Images/freepoint.png",
            HoverImage = "/Images/freepoint_hover.png"
            )]
        public static FreePoint CreateFreePoint()
        {
            return new FreePoint(0, 0).Name<FreePoint>("A");
        }

        [GObjectGenerator("Creates a point relative to another point",
            UIName = "Relative Point",
            StandardImage = "/Images/relativepoint.png",
            HoverImage = "/Images/relativepoint_hover.png"
            )]
        public static RelativePoint CreateRelativePoint(IGPoint p)
        {
            var i = PointNames.IndexOf(p.Name);
            var name = (i < PointNames.Length - 1) ? PointNames[i + 1].ToString() : null;
            return new RelativePoint(p, new Vector(10, 10)).Name<RelativePoint>(name);
        }

        [GObjectGenerator("Creates a point between two points", 
            UIName = "Mid Point",
            StandardImage = "/Images/midpoint.png",
            HoverImage = "/Images/midpoint_hover.png"
            )]
        public static MidPoint CreateMidPoint(IGPoint p, IGPoint q)
        {
            return new MidPoint(p, q, 0.5).Name<MidPoint>(null);
        }

        [GObjectGenerator("Creates a line segment between two points",
            UIName = "Line Segment",
            StandardImage = "/Images/linesegment.png",
            HoverImage = "/Images/linesegment_hover.png"
            )]
        public static LineSegment CreateLineSegment(IGPoint p, IGPoint q)
        {
            // TODO: Search for lines from p and q that intersect
            return new LineSegment(p, q).Name<LineSegment>(null);
        }

        [GObjectGenerator("Creates a line passing through two points",
            UIName = "Line",
            StandardImage = "/Images/line.png",
            HoverImage = "/Images/line_hover.png"
            )]
        public static Line CreateLine(IGPoint p, IGPoint q)
        {
            return new Line(p, q).Name<Line>(null);
        }

        [GObjectGenerator("Creates a point on the intersection of two lines", 
            UIName = "Intersection",
            StandardImage = "/Images/lineintersection.png",
            HoverImage = "/Images/lineintersection_hover.png"
            )]
        public static LineXLinePoint CreateIntersection(IGLine l, IGLine m)
        {
            return new LineXLinePoint(l, m).Name<LineXLinePoint>(null);
        }

        [GObjectGenerator("Creates a circle with its centre on one point and its perimeter passing through another point",
            UIName = "Compass",
            StandardImage = "/Images/compasscircle.png",
            HoverImage = "/Images/compasscircle_hover.png"
            )]
        public static CompassCircle CreateCircle(IGPoint o, IGPoint r)
        {
            return new CompassCircle(o, r).Name<CompassCircle>(null);
        }

        [GObjectGenerator("Creates the radical line of two circles",
            UIName = "Radical",
            StandardImage = "/Images/radicalline.png",
            HoverImage = "/Images/radicalline_hover.png"
            )]
        public static RadicalLine CreateRadical(IGCircle o, IGCircle r)
        {
            return new RadicalLine(o, r).Name<RadicalLine>(null);
        }

        [GObjectGenerator("Creates the circumcenter of three points", 
            UIName = "Circumcenter",
            StandardImage = "/Images/circumcenter.png",
            HoverImage = "/Images/circumcenter_hover.png"
            )]
        public static CircumCenter CreateCircumCenter(IGPoint p1, IGPoint p2, IGPoint p3)
        {
            return new CircumCenter(p1, p2, p3).Name<CircumCenter>(null);
        }

        [GObjectGenerator("Creates projection of a point on a line",
            UIName = "Orthogonal Projection",
            StandardImage = "/Images/orthogonalprojectionpoint.png",
            HoverImage = "/Images/orthogonalprojectionpoint_hover.png"
            )]
        public static OrthogonalProjectionPoint CreateProjection(IGPoint p, IGLine l)
        {
            return new OrthogonalProjectionPoint(p, l).Name<OrthogonalProjectionPoint>(null);
        }
        
        #endregion

        #region Name Helpers
        private static Dictionary<string, IGObject> NamedObjects = new Dictionary<string, IGObject>();

        private static bool CheckExistence(this string suggested)
        {
            return (String.IsNullOrEmpty(suggested) ||  NamedObjects.ContainsKey(suggested));
        }

        internal static void ClearNameRecords()
        {
            NamedObjects.Clear();
        }

        internal static void RecordName(IGObject generated, string name)
        {
            if (name.CheckExistence()) throw new ArgumentException();
            generated.Name = name;
            NamedObjects.Add(name, generated);
        }

        private static TGObject Name<TGObject>(this IGObject generated, string name) where TGObject:GObjectBase
        {
            if (name.CheckExistence())
            {
                int k = 1;
                while (NamedObjects.ContainsKey("Obj" + k.ToString())) k++;
                name = "Obj" + k.ToString();
            }
            RecordName(generated, name);
            return generated as TGObject;
        }

        private static readonly string PointNames = "ABCDEFGHIJKLMNOPRSTUVYZ";
        private static TGObject Name<TGObject>(this IGPoint generated, string name) where TGObject : GObjectBase
        {
            if (name.CheckExistence())
            {
                for (int i = 0; i < PointNames.Length; i++)
                {
                    string test = PointNames.Substring(i, 1);
                    if (!NamedObjects.ContainsKey(test))
                    {
                        RecordName(generated, test);
                        return generated as TGObject;
                    }
                }
                int k = 1;
                while (NamedObjects.ContainsKey("P" + k.ToString())) k++;
                name = "P" + k.ToString();
            }
            RecordName(generated, name);
            return generated as TGObject;
        }

        private static readonly string LineNames = "lmnoprstuvyzabcdefghijk";
        private static TGObject Name<TGObject>(this IGLine generated, string name) where TGObject : GObjectBase
        {
            if (name.CheckExistence())
            {
                for (int i = 0; i < LineNames.Length; i++)
                {
                    string test = LineNames.Substring(i, 1);
                    if (!NamedObjects.ContainsKey(test))
                    {
                        RecordName(generated, test);
                        return generated as TGObject;
                    }
                }
                int k = 1;
                while (NamedObjects.ContainsKey("l" + k.ToString())) k++;
                name = "l" + k.ToString();
            }
            RecordName(generated, name);
            return generated as TGObject;
        }

        #endregion
    }
}
